/**
 * kitjs模板，整合了知名模板 mustache.js
 * @class $Kit.Template
 * @requires kit.js
 * @see <a href="https://github.com/janl/mustache.js/blob/master/mustache.js">Source code</a>
 * @example <a href="http://mustache.github.com/mustache.5.html">Mustache官网example</a>
 * <a href="http://xueduany.github.com/KitJs/KitJs/demo/Template/demo.html">KitJsexample</a>
 */
$Kit.Template = function() {
	/*!
	 * mustache.js - Logic-less {{mustache}} templates with JavaScript
	 * http://github.com/janl/mustache.js
	 */
	var exports = {};
	exports.name = "mustache.js";
	exports.version = "0.5.0-dev";
	exports.tags = ["${", "}"];
	exports.parse = parse;
	exports.compile = compile;
	exports.render = render;
	exports.clearCache = clearCache;

	// This is here for backwards compatibility with 0.4.x.
	exports.to_html = function(template, view, partials, send) {
		var result = render(template, view, partials);

		if( typeof send === "function") {
			send(result);
		} else {
			return result;
		}
	};
	var _toString = Object.prototype.toString;
	var _isArray = Array.isArray;
	var _forEach = Array.prototype.forEach;
	var _trim = String.prototype.trim;

	var isArray;
	if(_isArray) {
		isArray = _isArray;
	} else {
		isArray = function(obj) {
			return _toString.call(obj) === "[object Array]";
		};
	}

	var forEach;
	if(_forEach) {
		forEach = function(obj, callback, scope) {
			return _forEach.call(obj, callback, scope);
		};
	} else {
		forEach = function(obj, callback, scope) {
			for(var i = 0, len = obj.length; i < len; ++i) {
				callback.call(scope, obj[i], i, obj);
			}
		};
	}

	var spaceRe = /^\s*$/;

	function isWhitespace(string) {
		return spaceRe.test(string);
	}

	var trim;
	if(_trim) {
		trim = function(string) {
			return string == null ? "" : _trim.call(string);
		};
	} else {
		var trimLeft, trimRight;

		if(isWhitespace("\xA0")) {
			trimLeft = /^\s+/;
			trimRight = /\s+$/;
		} else {
			// IE doesn't match non-breaking spaces with \s, thanks jQuery.
			trimLeft = /^[\s\xA0]+/;
			trimRight = /[\s\xA0]+$/;
		}
		trim = function(string) {
			return string == null ? "" : String(string).replace(trimLeft, "").replace(trimRight, "");
		};
	}

	var escapeMap = {
		"&" : "&amp;",
		"<" : "&lt;",
		">" : "&gt;",
		'"' : '&quot;',
		"'" : '&#39;'
	};

	function escapeHTML(string) {
		return String(string).replace(/&(?!\w+;)|[<>"']/g, function(s) {
			return escapeMap[s] || s;
		});
	}

	/**
	 * Adds the `template`, `line`, and `file` properties to the given error
	 * object and alters the message to provide more useful debugging information.
	 */
	function debug(e, template, line, file) {
		file = file || "<template>";

		var lines = template.split("\n"), start = Math.max(line - 3, 0), end = Math.min(lines.length, line + 3), context = lines.slice(start, end);

		var c;
		for(var i = 0, len = context.length; i < len; ++i) {
			c = i + start + 1;
			context[i] = (c === line ? " >> " : "    ") + context[i];
		}

		e.template = template;
		e.line = line;
		e.file = file;
		e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");

		return e;
	}

	/**
	 * Looks up the value of the given `name` in the given context `stack`.
	 */
	function lookup(name, stack, defaultValue) {
		if(name === ".") {
			return stack[stack.length - 1];
		}

		var names = name.split(".");
		var lastIndex = names.length - 1;
		var target = names[lastIndex];

		var value, context, i = stack.length, j, localStack;
		while(i) {
			localStack = stack.slice(0);
			context = stack[--i];
			j = 0;
			while(j < lastIndex) {
				context = context[names[j++]];

				if(context == null) {
					break;
				}

				localStack.push(context);
			}

			if(context && typeof context === "object" && target in context) {
				value = context[target];
				break;
			}
		}

		// If the value is a function, call it in the current context.
		if( typeof value === "function") {
			value = value.call(localStack[localStack.length - 1]);
		}

		if(value == null) {
			return defaultValue;
		}

		return value;
	}

	function renderSection(name, stack, callback, inverted) {
		var buffer = "";
		var value = lookup(name, stack);

		if(inverted) {
			// From the spec: inverted sections may render text once based on the
			// inverse value of the key. That is, they will be rendered if the key
			// doesn't exist, is false, or is an empty list.
			if(value == null || value === false || (isArray(value) && value.length === 0)) {
				buffer += callback();
			}
		} else if(isArray(value)) {
			forEach(value, function(value) {
				stack.push(value);
				buffer += callback();
				stack.pop();
			});
		} else if( typeof value === "object") {
			stack.push(value);
			buffer += callback();
			stack.pop();
		} else if( typeof value === "function") {
			var scope = stack[stack.length - 1];
			var scopedRender = function(template) {
				return render(template, scope);
			};
			buffer += value.call(scope, callback(), scopedRender) || "";
		} else if(value) {
			buffer += callback();
		}

		return buffer;
	}

	/**
	 * Parses the given `template` and returns the source of a function that,
	 * with the proper arguments, will render the template. Recognized options
	 * include the following:
	 *
	 *   - file     The name of the file the template comes from (displayed in
	 *              error messages)
	 *   - tags     An array of open and close tags the `template` uses. Defaults
	 *              to the value of Mustache.tags
	 *   - debug    Set `true` to log the body of the generated function to the
	 *              console
	 *   - space    Set `true` to preserve whitespace from lines that otherwise
	 *              contain only a {{tag}}. Defaults to `false`
	 */
	function parse(template, options) {
		options = options || {};

		var tags = options.tags || exports.tags, openTag = tags[0], closeTag = tags[tags.length - 1];

		var code = ['var buffer = "";', // output buffer
		"\nvar line = 1;", // keep track of source line number
		"\ntry {", '\nbuffer += "'];

		var spaces = [], // indices of whitespace in code on the current line
		hasTag = false, // is there a {{tag}} on the current line?
		nonSpace = false;
		// is there a non-space char on the current line?

		// Strips all space characters from the code array for the current line
		// if there was a {{tag}} on it and otherwise only spaces.
		var stripSpace = function() {
			if(hasTag && !nonSpace && !options.space) {
				while(spaces.length) {
					code.splice(spaces.pop(), 1);
				}
			} else {
				spaces = [];
			}
			hasTag = false;
			nonSpace = false;
		};
		var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;

		var setTags = function(source) {
			tags = trim(source).split(/\s+/);
			nextOpenTag = tags[0];
			nextCloseTag = tags[tags.length - 1];
		};
		var includePartial = function(source) {
			code.push('";', updateLine, '\nvar partial = partials["' + trim(source) + '"];', '\nif (partial) {', '\n  buffer += render(partial,stack[stack.length - 1],partials);', '\n}', '\nbuffer += "');
		};
		var openSection = function(source, inverted) {
			var name = trim(source);

			if(name === "") {
				throw debug(new Error("Section name may not be empty"), template, line, options.file);
			}

			sectionStack.push({
				name : name,
				inverted : inverted
			});

			code.push('";', updateLine, '\nvar name = "' + name + '";', '\nvar callback = (function () {', '\n  return function () {', '\n    var buffer = "";', '\nbuffer += "');
		};
		var openInvertedSection = function(source) {
			openSection(source, true);
		};
		var closeSection = function(source) {
			var name = trim(source);
			var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;

			if(!openName || name != openName) {
				throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
			}

			var section = sectionStack.pop();

			code.push('";', '\n    return buffer;', '\n  };', '\n})();');

			if(section.inverted) {
				code.push("\nbuffer += renderSection(name,stack,callback,true);");
			} else {
				code.push("\nbuffer += renderSection(name,stack,callback);");
			}

			code.push('\nbuffer += "');
		};
		var sendPlain = function(source) {
			code.push('";', updateLine, '\nbuffer += lookup("' + trim(source) + '",stack,"");', '\nbuffer += "');
		};
		var sendEscaped = function(source) {
			code.push('";', updateLine, '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));', '\nbuffer += "');
		};
		var line = 1, c, callback;
		for(var i = 0, len = template.length; i < len; ++i) {
			if(template.slice(i, i + openTag.length) === openTag) {
				i += openTag.length;
				c = template.substr(i, 1);
				updateLine = '\nline = ' + line + ';';
				nextOpenTag = openTag;
				nextCloseTag = closeTag;
				hasTag = true;

				switch (c) {
					case "!":
						// comment
						i++;
						callback = null;
						break;
					case "=":
						// change open/close tags, e.g. {{=<% %>=}}
						i++;
						closeTag = "=" + closeTag;
						callback = setTags;
						break;
					case ">":
						// include partial
						i++;
						callback = includePartial;
						break;
					case "#":
						// start section
						i++;
						callback = openSection;
						break;
					case "^":
						// start inverted section
						i++;
						callback = openInvertedSection;
						break;
					case "/":
						// end section
						i++;
						callback = closeSection;
						break;
					case "{":
						// plain variable
						closeTag = "}" + closeTag;
					// fall through
					case "&":
						// plain variable
						i++;
						nonSpace = true;
						callback = sendPlain;
						break;
					default:
						// escaped variable
						nonSpace = true;
						callback = sendEscaped;
				}

				var end = template.indexOf(closeTag, i);

				if(end === -1) {
					throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
				}

				var source = template.substring(i, end);

				if(callback) {
					callback(source);
				}

				// Maintain line count for \n in source.
				var n = 0;
				while(~( n = source.indexOf("\n", n))) {
					line++;
					n++;
				}
				i = end + closeTag.length - 1;
				openTag = nextOpenTag;
				closeTag = nextCloseTag;
			} else {
				c = template.substr(i, 1);

				switch (c) {
					case '"':
					case "\\":
						nonSpace = true;
						code.push("\\" + c);
						break;
					case "\r":
						// Ignore carriage returns.
						break;
					case "\n":
						spaces.push(code.length);
						code.push("\\n");
						stripSpace();
						// Check for whitespace on the current line.
						line++;
						break;
					default:
						if(isWhitespace(c)) {
							spaces.push(code.length);
						} else {
							nonSpace = true;
						}

						code.push(c);
				}
			}
		}

		if(sectionStack.length != 0) {
			throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
		}

		// Clean up any whitespace from a closing {{tag}} that was at the end
		// of the template without a trailing \n.
		stripSpace();

		code.push('";', "\nreturn buffer;", "\n} catch (e) { throw {error: e, line: line}; }");

		// Ignore `buffer += "";` statements.
		var body = code.join("").replace(/buffer \+= "";\n/g, "");

		if(options.debug) {
			if( typeof console != "undefined" && console.log) {
				console.log(body);
			} else if( typeof print === "function") {
				print(body);
			}
		}

		return body;
	}

	/**
	 * Used by `compile` to generate a reusable function for the given `template`.
	 */
	function _compile(template, options) {
		var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
		var body = parse(template, options);
		var fn = new Function(args, body);

		// This anonymous function wraps the generated function so we can do
		// argument coercion, setup some variables, and handle any errors
		// encountered while executing it.
		return function(view, partials) {
			partials = partials || {};

			var stack = [view];
			// context stack

			try {
				return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
			} catch (e) {
				throw debug(e.error, template, e.line, options.file);
			}
		};
	}

	// Cache of pre-compiled templates.
	var _cache = {};

	/**
	 * Clear the cache of compiled templates.
	 */
	function clearCache() {
		_cache = {};
	}

	/**
	 * Compiles the given `template` into a reusable function using the given
	 * `options`. In addition to the options accepted by Mustache.parse,
	 * recognized options include the following:
	 *
	 *   - cache    Set `false` to bypass any pre-compiled version of the given
	 *              template. Otherwise, a given `template` string will be cached
	 *              the first time it is parsed
	 */
	function compile(template, options) {
		options = options || {};

		// Use a pre-compiled version from the cache if we have one.
		if(options.cache !== false) {
			if(!_cache[template]) {
				_cache[template] = _compile(template, options);
			}

			return _cache[template];
		}

		return _compile(template, options);
	}

	/**
	 * High-level function that renders the given `template` using the given
	 * `view` and `partials`. If you need to use any of the template options (see
	 * `compile` above), you must compile in a separate step, and then call that
	 * compiled function.
	 */
	function render(template, view, partials) {
		return compile(template)(view, partials);
	}

	return exports;
}
/**
 * $Kit.Template的实例，直接通过这个实例访问$Kit.Template所有方法
 * @global
 * @alias $kit.template
 * @type $Kit.Template
 */
$kit.template = new $Kit.Template();
